﻿//  --------------------------------
//  Copyright (c) AW Systems, Inc. All rights reserved.
//  This source code is made available under the terms of the Microsoft Public License (Ms-PL)
//  http://www.codeplex.com/SLAM/license
//  ---------------------------------

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
using Microsoft.SharePoint.WebControls;
using Microsoft.SharePoint;
using AWS.SLAM.Events;
using AWS.SLAM.SharePoint;
using AWS.SLAM.Configuration;
using System.Globalization;
using System.Reflection;
using System.Threading;

namespace AWS.SLAM.FieldTypes
{
	/// <summary>
	/// SlamIdField rendering template control.
	/// </summary>
	public partial class SlamIdFieldControl : BaseFieldControl
	{
		protected Literal ltSlamId;
		protected TextBox txtSlamId;

		/// <summary>
		/// Indicates whether this field should automatically generate a SLAM ID
		/// </summary>
		public bool GenerateValue
		{
			get;
			set;
		}

		/// <summary>
		/// Indicates whether the interface for assigning the value of the Field is visible
		/// </summary>
		public bool ShowInForms
		{
			get;
			set;
		}

		/// <summary>
		/// Returns assigned value of the Field
		/// </summary>
		public override object Value
		{
			get
			{
				this.EnsureChildControls();
				return txtSlamId.Text;
			}
		}

		protected override string DefaultTemplateName
		{
			get
			{
				return "SLAMIDFieldControl";
			}
		}

		public override string DisplayTemplateName
		{
			get
			{
				return "SLAMIDFieldControl";
			}
		}

		/// <summary>
		/// Performs Item New/Edit Form validation relevant to SLAM.
		/// 
		/// If the configured type associated with the current Item has required associations,
		/// verify that the lookup fields corresponding to those associations have values.
		/// </summary>
		public override void Validate()
		{
			base.Validate();
			if (((base.ControlMode != SPControlMode.Display) && base.IsValid))
			{	
				IConfigurationManager slamConfiguration = ComponentService.GetInstance<IConfigurationManager>();
				DataMapping currentDataMapping = slamConfiguration.DataMapping;
				string siteCollection = SPControl.GetContextSite(HttpContext.Current).ServerRelativeUrl.TrimStart('/');
				if (!String.IsNullOrEmpty(siteCollection) && slamConfiguration.DataMappings.ContainsKey(siteCollection))
				{
					currentDataMapping = slamConfiguration.DataMappings[siteCollection];
				}

				ConfiguredType type = null;
				SPSecurity.RunWithElevatedPrivileges(delegate()
				{
					//GetSlamConfiguredType traverses entire site collection, use elevated privileges
					//to perform this operation
					using (SPSite elevatedSite = new SPSite(Web.Site.ID))
					{
						using (SPWeb elevatedWeb = elevatedSite.AllWebs[Web.ID])
						{
							SPList elevatedList = elevatedWeb.Lists[ListId];
							SPListItem elevatedListItem = elevatedList.Items.Add();
							elevatedListItem["ContentTypeId"] = Item["ContentTypeId"];
							type = elevatedListItem.GetSlamConfiguredType();
						}
					}
				});
				//For every association field configured as Required in SLAM, verify the corresponding field has a selected value
				foreach (AssociationField field in currentDataMapping.GetTypeAssociations(type.Id, type.Name).Where(a => a.Required == true))
				{	
					foreach (BaseFieldControl control in SPContext.Current.FormContext.FieldControlCollection)
					{
						if (control.Field != null && ((control is LookupField && control.Value == null) || (control is MultipleLookupField && (control.Value == null || ((SPFieldLookupValueCollection)control.Value).Count == 0))) && (control.Field.Title == field.Name || control.Field.InternalName == field.Name))
						{
							control.ErrorMessage = "This field is configured in SLAM as required and is missing a value.";
							control.IsValid = false;
							base.IsValid = false;
						}
					}
				}
			}
		}

		protected override void CreateChildControls()
		{
			if (Field != null)
			{
				base.CreateChildControls();

				ltSlamId = (Literal)TemplateContainer.FindControl("ltSlamId");
				txtSlamId = (TextBox)TemplateContainer.FindControl("txtSlamId");

				if (ltSlamId != null && txtSlamId != null)
				{
					txtSlamId.Visible = false;

					if (GenerateValue && (ControlMode == SPControlMode.New || ItemFieldValue == null)) //Auto-Generate SLAM ID
					{
						object value = "";
						if (!Page.IsPostBack)
						{
							//GetUniqueId(true, true) traverses entire site collection, use elevated privileges
							//to perform this operation
							SPSecurity.RunWithElevatedPrivileges(delegate()
							{
								using (SPSite elevatedSite = new SPSite(Web.Site.ID))
								{
									using (SPWeb elevatedWeb = elevatedSite.AllWebs[Web.ID])
									{
										SPList elevatedList = elevatedWeb.Lists[ListId];
										SPListItem elevatedListItem = null;
										if (ControlMode != SPControlMode.New)
										{	
											try
											{
												elevatedListItem = elevatedList.Items.GetItemById(ItemId);
											}
											catch { }
										}
										if (elevatedListItem == null)
										{
											elevatedListItem = elevatedList.Items.Add();
										}
										elevatedListItem["ContentTypeId"] = Item["ContentTypeId"];
										value = elevatedListItem.GetUniqueId(true, true);
									}
								}
							});
							ViewState["FieldValue"] = value;
						}
						else
						{
							value = (object)ViewState["FieldValue"];
						}
						ltSlamId.Text = value.ToString();
					}
					else if (!GenerateValue && (ControlMode == SPControlMode.New || ItemFieldValue == null)) //Manually assign SLAM ID
					{
						ltSlamId.Visible = false;
						txtSlamId.Visible = true;
					}
					else if (ItemFieldValue != null) //SLAM ID is assigned
					{
						ltSlamId.Text = Convert.ToString(ItemFieldValue);
					}
				}

				if (!Page.IsPostBack || GenerateValue)
				{
					txtSlamId.Text = ltSlamId.Text;
				}
			}
		}

		protected override void OnInit(EventArgs e)
		{
			base.OnInit(e);

			ConfiguredType type = null;

			//GetSlamConfiguredType traverses entire site collection, use elevated privileges
			//to perform this operation
			SPSecurity.RunWithElevatedPrivileges(delegate()
			{
				using (SPSite elevatedSite = new SPSite(Web.Site.ID))
				{
					using (SPWeb elevatedWeb = elevatedSite.AllWebs[Web.ID])
					{
						SPList elevatedList = elevatedWeb.Lists[ListId];

						if (!elevatedList.Fields.ContainsField("SLAM Properties"))
						{
							elevatedList.AddSlamPropertiesField();
							Page.Response.Redirect(Page.Request.Url.ToString());
						}
						SPListItem elevatedListItem = elevatedList.Items.Add();
						elevatedListItem["ContentTypeId"] = Item["ContentTypeId"];
						type = elevatedListItem.GetSlamConfiguredType();
					}
				}
			});

			//If the configured type associated with the current Item is set
			//to be updated synchronously, set OnSaveHandler
			if (type != null && type.SynchronousUpdates &&
			   (SPContext.Current.FormContext.FormMode == SPControlMode.New ||
				SPContext.Current.FormContext.FormMode == SPControlMode.Edit) &&
				SPContext.Current.FormContext.OnSaveHandler == null)
			{
				SPContext.Current.FormContext.OnSaveHandler += new EventHandler(this.SaveItem);
			}

			//Make this control instance easily accessible in Page.Items
			if (Page != null)
			{
				Page.Items[typeof(SlamIdFieldControl)] = this;
			}
		}

		/// <summary>
		/// Returns the SlamIdFieldControl for the SLAM ID field, if available, on the given Page.
		/// </summary>
		/// <param name="page">Page containing the SlamIdFieldControl</param>
		/// <returns>The SlamIdFieldControl on the given Page</returns>
		public static SlamIdFieldControl GetCurrent(Page page)
		{
			//Check the Page.Items collection first, if not found traverse the entire control tree of the Page
			SlamIdFieldControl idControl = null;
			if (page.Items[typeof(SlamIdFieldControl)] != null)
			{
				idControl = (SlamIdFieldControl)page.Items[typeof(SlamIdFieldControl)];
			}
			else
			{
				SlamIdFieldControl fieldControl = FindSlamIdFieldControl(page.Form);
				if (fieldControl != null)
				{
					idControl = fieldControl;
				}
			}
			return idControl;
		}

		/// <summary>
		/// Recursively traverses given control and children to find a SlamIdFieldControl.
		/// </summary>
		/// <param name="control">Control to check for SlamIdFieldControl</param>
		/// <returns>Found SlamIdFieldControl</returns>
		private static SlamIdFieldControl FindSlamIdFieldControl(Control control)
		{
			SlamIdFieldControl fieldControl = null;
			foreach (Control childControl in control.Controls)
			{
				if (childControl is SlamIdFieldControl)
				{
					fieldControl = childControl as SlamIdFieldControl;
					break;
				}
				else if (childControl.Controls.Count > 0)
				{
					fieldControl = FindSlamIdFieldControl(childControl);
					if (fieldControl != null)
					{
						break;
					}
				}
			}
			return fieldControl;
		}

		protected override void OnLoad(EventArgs e)
		{
			//Make sure field "container" is visible, do BaseFieldControl.OnLoad, 
			//then determine whether this rendering template control should be rendered
			this.Parent.Parent.Visible = true;
			base.OnLoad(e);
			this.Parent.Parent.Visible = ShowInForms;
		}
		
		/// <summary>
		/// Method for use with FormContext.OnSaveHandler.  Saves the current ListItem and performs SLAM operation synchronously.
		/// </summary>
		/// <param name="sender"></param>
		/// <param name="e"></param>
		protected void SaveItem(object sender, EventArgs e)
		{	
			//ListItem property SLAMSynchronousUpdate indicates to SLAM event handlers
			//that the incoming SPListItem is processed by the SLAM ID field control
			SPContext.Current.Web.AllowUnsafeUpdates = true;
			SPContext.Current.ListItem.SetSlamProperty("SynchronousUpdate", true);
			if (ControlMode == SPControlMode.Edit)
			{
				SPContext.Current.ListItem.SetSlamProperty("PreviousContentTypeId", SPContext.Current.ListItem.ContentTypeId.ToString());
			}
			
			//Execute delegates assigned to PreSaveItem event
			OnPreSaveItem(e);
			
			//ItemContext.FormContext.UploadMode is not a public property, but if
			//the current ListItem is a document library item, the UploadMode value must properly
			//be passed to SaveButton.SaveItem.  Therefore, get UploadMode using reflection
			Type spFormContextType = typeof(SPFormContext);
			PropertyInfo spFormContextUploadMode = spFormContextType.GetProperty("UploadMode", BindingFlags.Instance | BindingFlags.NonPublic);

			bool uploadMode = (bool)spFormContextUploadMode.GetValue(base.ItemContext.FormContext, null);
			bool itemSaved = SaveButton.SaveItem(SPContext.Current, uploadMode, Page.Request.QueryString["CheckInComment"] ?? "");

			if (itemSaved)
			{
				try
				{
					//If this is a document library item, it is possible that the SlamUpdate call will throw a "item modified by..." exception.  To mitigate the risk of it
					//occurring wait for a moment
					if (ListItem.File != null)
					{
						Thread.Sleep(200);
					}

					if (ControlMode == SPControlMode.New)
					{
						SPContext.Current.ListItem.SlamUpdate(true, false, false, SPContext.Current.Web.CurrentUser.LoginName, true);
					}
					else
					{
						SPContext.Current.ListItem.SlamUpdate(true, false, false, SPContext.Current.Web.CurrentUser.LoginName, false);
					}
				}
				catch(Exception ex)
				{
					throw new SPException(ex.ToString(), 1);
				}
				//Execute delegates assigned to PostSaveItem event
				OnPostSaveItem(e);
			}
			else
			{
				//If SaveButton.SaveItem fails, throw an SPException with an ErrorCode of 0.  The SLAM SaveButton will
				//catch this exception and prevent the form from redirecting and present relevant errors to the user.
				throw new SPException("SaveButton.SaveItem failed", 0);
			}
		}

		private static readonly object EventPreSaveItem = new object();

		/// <summary>
		/// PreSaveItem event provides hook for delegates to be executed prior to the saving of a
		/// ListItem.
		/// </summary>
		public event EventHandler PreSaveItem
		{
			add
			{
				base.Events.AddHandler(SlamIdFieldControl.EventPreSaveItem, value);
			}
			remove
			{
				base.Events.RemoveHandler(SlamIdFieldControl.EventPreSaveItem, value);
			}
		}

		protected virtual void OnPreSaveItem(EventArgs e)
		{
			EventHandler eventHandler = (EventHandler)base.Events[SlamIdFieldControl.EventPreSaveItem];
			if (eventHandler != null)
			{
				eventHandler(this, e);
			}
		}

		private static readonly object EventPostSaveItem = new object();

		/// <summary>
		/// PostSaveItem event provides hook for delegates to be executed after the saving of a
		/// ListItem.
		/// </summary>
		public event EventHandler PostSaveItem
		{
			add
			{
				base.Events.AddHandler(SlamIdFieldControl.EventPostSaveItem, value);
			}
			remove
			{
				base.Events.RemoveHandler(SlamIdFieldControl.EventPostSaveItem, value);
			}
		}

		protected virtual void OnPostSaveItem(EventArgs e)
		{
			EventHandler eventHandler = (EventHandler)base.Events[SlamIdFieldControl.EventPostSaveItem];
			if (eventHandler != null)
			{
				eventHandler(this, e);
			}
		}
	}
}